package jvlite.ioc;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import jvlite.util.LogUtil;

/**
 * scan for all the class under specific package
 * @author Administrator
 *
 */
public class ClassHandler {
	
	private static Set<Class<?>> classes;
	
	public static Set<Class<?>> getClasses(String pack) {
		classes = new LinkedHashSet<Class<?>>();
		// the flag which indicate to recursive or not
		Boolean recursive = true;
		// obtain the package name for scan
		String packageName = pack;
		String packageDirName = packageName.replace('.', '/');
		// define a enumeration to deal with the file inside the main package with loop
		Enumeration<URL> dirs;
		try {
			dirs = Thread.currentThread().getContextClassLoader()
					.getResources(packageDirName);
			// loop begin
			while (dirs.hasMoreElements()) {
				// obtain the next element
				URL url = dirs.nextElement();
				// obtain the protocol
				String protocol = url.getProtocol();
				// save on server if file
				if ("file".equals(protocol)) {
					LogUtil.info("scan commmon files");
					// obtain the physical path
					String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
					// scan as type of file and add them into the enumeration one by one
					ClassHandler.findAndAddClassesInPackageByFile(packageName, filePath,
							recursive, classes);
				} else if ("jar".equals(protocol)) {
					// if it is a JAR package
					// define
					LogUtil.info("scan JAR files");
					JarFile jar;
					try {
						// obtain jar
						jar = ((JarURLConnection) url.openConnection())
								.getJarFile();
						// create a enumeration from the JAR package
						Enumeration<JarEntry> entries = jar.entries();
						// loop as before
						while (entries.hasMoreElements()) {
							// obtain a object such as directory, JAR file, META-INF files or something else from the directory
							JarEntry entry = entries.nextElement();
							String name = entry.getName();
							// if it is begin with /
							if (name.charAt(0) == '/') {
								// get the string after /
								name = name.substring(1);
							}
							// if the front park is same as package name 
							if (name.startsWith(packageDirName)) {
								int idx = name.lastIndexOf('/');
								// if ends with / then it is a package
								if (idx != -1) {
									// get the package name and replace "/" with "."
									packageName = name.substring(0, idx)
											.replace('/', '.');
								}
								// if it is a package and is available to recursive
								if ((idx != -1) || recursive) {
									// if is a class file instead of package
									if (name.endsWith(".class")
											&& !entry.isDirectory()) {
										// remove ".class" to get the real name
										String className = name.substring(
												packageName.length() + 1,
												name.length() - 6);
										try {
											// put the class file into classes set
											classes.add(Class
													.forName(packageName + '.'
															+ className));
										} catch (ClassNotFoundException e) {
											e.printStackTrace();
										}
									}
								}
							}
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return classes;
	}

	public static void findAndAddClassesInPackageByFile(String packageName,
			String packagePath, final boolean recursive, Set<Class<?>> classes) {
		// obtain the directory to make a JAVA file object
		File dir = new File(packagePath);
		// if it is not exists or not a directory 
		if (!dir.exists() || !dir.isDirectory()) {
			return;
		}
		// if it is exists then list the file inside the directory
		File[] dirfiles = dir.listFiles(new FileFilter() {
			// define the filter rule, if it is able to iterate(has sub directory) or it is end with '.class' so it is a class file
			@Override
			public boolean accept(File file) {
				return (recursive && file.isDirectory())
						|| (file.getName().endsWith(".class"));
			}
		});
		// iterate all the files
		for (File file : dirfiles) {
			// if it is a directory then continue
			if (file.isDirectory()) {
				findAndAddClassesInPackageByFile(
						packageName + "." + file.getName(),
						file.getAbsolutePath(), recursive, classes);
			} else {
				// if it is a JAVA class file then remove .class for only real name remained
				String className = file.getName().substring(0,
						file.getName().length() - 6);
				try {
					// put into the classes set
					// classes.add(Class.forName(packageName + '.' +
					// className));
					// it is better to use class_loader than class.forName due to the second one would trigger off static methods inside it 
					classes.add(Thread.currentThread().getContextClassLoader()
							.loadClass(packageName + '.' + className));
				} catch (ClassNotFoundException e) {
					// log.error("add custom viewer class failed: could not find the class file");
					e.printStackTrace();
				}
			}
		}
	}

	public static Set<Class<?>> getClasses() {
		return classes;
	}

	public static void setClasses(Set<Class<?>> classes) {
		ClassHandler.classes = classes;
	}
	
}